#include "BufferManager.h"

#include <string.h>	// declares NULL
#include <stdio.h> // printf
#include <stdlib.h> // free

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

// Constructor
BufferManager::BufferManager(HANDLE mutex) {
	this->mutex = mutex;
	this->count = 0;

	// blank out link list of Buffers
	this->head = this->tail = NULL;
}

// Deconstructor
BufferManager::~BufferManager() {
//	printf("BufferManager deconstructor\r\n");
	// clear out the current Buffers
	clear();
//	dump();
}


// clear out the current Buffers
void BufferManager::clear() {
	Buffer *tmp;
	while(this->head != NULL) {
		tmp = this->head;
		this->head = this->head->next;

		if(tmp->data != NULL) {
			delete[] tmp->data;
		}
		delete tmp;
	}

	// blank out link list of Buffers
	this->head = this->tail = NULL;
}

bool BufferManager::addBytes(char *bytes, int count) {
//	printf("addbytes %d\n", count);

	// copy off into a buffer so that the 'strchr' is safe
	char *data = new char[count+1];
	strncpy(data, bytes, count);
	data[count] = 0;

	// create new Buffer
	Buffer *buffer = new Buffer;
	buffer->data = NULL;
	// default values for linked list
	buffer->prev = buffer->next = NULL;
	// default isReady
	buffer->isReady = false;

	// Make two nodes if there is a '\n' and more bytes afterwards,
	// a single node otherwise.
	char *ptr = strchr(data, '\n');
	if(ptr != NULL) {
		int size = ptr - data + 1;
		if(size < count) {
			// there are more bytes, need to form two nodes
			ptr ++;
			char tmp = ptr[0];
			ptr[0] = 0;
			this->addBytes(data, strlen(data));
			ptr[0] = tmp;
			this->addBytes(ptr, strlen(ptr));
			// clean up allocated memory
			delete[] data;
			delete buffer;

			// ok, we exit at this point, our data was handled by the two calls
			return true;
		}

		// flag this Buffer as ready to be used
		buffer->isReady = true;	// TODO (may need to merge though)

		// chop off the '\n'
		count --;
		// chop off the '\r' if it exists
		if(strchr(data, '\r') != NULL) {
			count --;
		}
	}

	// Store data for this Buffer
	buffer->data = new char[count + 1];
	buffer->count = count;
	strncpy(buffer->data, bytes, count);
	buffer->data[count] = 0;
//	printf("add bytes: '%s'\n", buffer->data);

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	// attach new Buffer to link list
	if(this->head == NULL) {
		// this is our first Buffer
		this->head = this->tail = buffer;
	}
	else {
		// otherwise, attach to end of link list
		this->tail->next = buffer;
		buffer->prev = this->tail;
		buffer->next = NULL;
		this->tail = buffer;
	}

	// remember current overall count
	this->count += count;

	// Need to merge buffers if one doesn't have '\n'
	this->merge(this->head);

	// Release the mutex
	ReleaseMutex(this->mutex);

	delete[] data;

	// just always returns true for now
	return true;
}

// within a mutex hold, so everything here has exclusive access
void BufferManager::merge(Buffer *node) {
	// Check for common scenarios which do not result in a merge
	if(node == NULL) {
		return;
	}
	// a 2nd node is required for a merge to be possible
	if(node->next == NULL) {
		return;
	}
	// only merge up to '\n', then start merging using next node
	if(node->isReady) {
		this->merge(node->next);
		return;
	}

	// We have 2 buffers and the first does not have '\n', combine them
	Buffer *other = node->next;

	// First put together the merged char*
	char *data = new char[node->count + other->count + 1];
	strncpy(data, node->data, node->count);
	strncpy(data + node->count, other->data, other->count);
	data[node->count + other->count] = 0;

	// Free up the old data of node 1
	if(node->data != NULL) {
		delete[] node->data;
	}

	// Allocate and copy the merged string into node 1
	node->data = new char[node->count + other->count + 1];
	strncpy(node->data, data, node->count + other->count);
	node->data[node->count + other->count] = 0;
	node->count += other->count;

	// Free up our temporary data pointer
	delete[] data;

	// Pop out the merged node
	Buffer *tmp;
	tmp = node->next;
	node->next = tmp->next;
	if(node->next != NULL) {
		node->next->prev = node;
	}
	if(tmp == this->tail) {
		this->tail = node;
	}

	// Check if this node is ready
	if(tmp->isReady) {
		node->isReady = true;
	}

	// Free up the memory used by no-longer needed node 2
	if(tmp->data != NULL) {
		delete[] tmp->data;
	}
	delete tmp;

	// Additional merges may be required before a '\n' is obtained
	this->merge(node);
}

bool BufferManager::isReady(void) {
	bool isReady = false;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	if(this->head != NULL) {
		isReady = this->head->isReady;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);
	
	return isReady;
}

// returns a pointer to the current available string,
// or NULL if none is available
char* BufferManager::getNext() {
	char *data = NULL;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	if(this->head != NULL) {
		data = this->head->data;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);

	return data;
}

// removes current available string
void BufferManager::pop() {
	long count = 0;

	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	Buffer *tmp;
	if(this->head != NULL) {
		tmp = this->head;
		this->head = this->head->next;
		if(this->head != NULL) {
			this->head->prev = NULL;
		}
		if(this->tail == tmp) {
			this->tail = this->head;
		}

		if(tmp->data != NULL) {
			delete[] tmp->data;
		}

		count = tmp->count;
		delete tmp;
	}

	// remember current overall count
	this->count -= count;

	// Release the mutex
	ReleaseMutex(this->mutex);
}

// display buffer link-list
void BufferManager::dump() {
	// Request ownership of mutex
	WaitForSingleObject(this->mutex, INFINITE);

	Buffer *slider = this->head;
	printf("[dump]\r\n");
	while(slider != NULL) {
		printf("> '%s'\r\n", slider->data);
		slider = slider->next;
	}

	// Release the mutex
	ReleaseMutex(this->mutex);
}